/*
 * Copyright 2007-2008 Michele Mostarda ( michele.mostarda@gmail.com ).
 * All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.asemantics.anthill;

import org.jivesoftware.smack.packet.Message;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Defines an <i>Ant</i> node able to prepare {@link CommandMessage}s.
 *
 * @author Michele Mostarda
 */
public abstract class RemoteOperation {

    /**
     * The name of the property containing the remote operation type.
     */
    protected static final String REMOTE_OPERATION_TYPE = "remote_operation_type";

    /**
     * The context name for any extending target.
     */
    private String context;

    /**
     * This flag sais whether the command should fail if the <i>context</i>
     * listener is offline.
     */
    private boolean failOnContextOffline;

    /**
     * The defualt empty constructor.
     */
    public RemoteOperation() {}

    /**
     * Returns the CommandMessage to be sent.
     *
     * @return
     */
    protected abstract CommandMessage prepareMessageInternal();

    /**
     * Returns the prepared message adding the RemoteOperation description.
     * @return
     */
    public CommandMessage prepareMessage() {
        CommandMessage commandMessage = prepareMessageInternal();
        String type = this.getClass().getName();
        commandMessage.setProperty(REMOTE_OPERATION_TYPE, type);
        return commandMessage;
    }

    /**
     * Executes the current operation with the command message parameters on the listener task environment.
     *
     * @return
     */
    public abstract CommandResponseMessage processCommand(CommandListenerTask clt, CommandMessage commandMessage);

    /**
     * Processes the returned response.
     *
     * @param commanderTask the task environment.
     * @param response
     */
    public abstract void processResult(final CommanderTask commanderTask, final CommandResponseMessage response);

    /**
     * Returns the context.
     *
     * @return
     */
    public String getContext() {
        return context;
    }

    /**
     * Sets the context.
     *
     * @param context
     */
    public void setContext(String context) {
        CommanderTask.validateAttribute(context, "context");
        this.context = context;
    }

    /**
     * Sets the fail on content offline flag.
     *
     * @param flag
     */
    public void setFailOnContextOffline(boolean flag) {
        failOnContextOffline = flag;
    }

    /**
     * Returns the fail on content offline flag.
     *
     * @return
     */
    public boolean isFailOnContextOffline() {
        return failOnContextOffline;
    }

    /**
     * Check if the node configuration is valid.
     */
    protected void validateConfiguration() {
        if( context == null ) {
            throw new IllegalArgumentException("context must be specified");
        }
    }

    /**
     * Takes a low level message and loads the associated
     * <i>RemoteOperation</i> object.
     *
     * @param message
     * @return
     */
    protected static RemoteOperation deserializeRemoteOperation(Message message) {
        String operationType = (String) message.getProperty(REMOTE_OPERATION_TYPE);
        if(operationType == null) {
            throw new IllegalArgumentException("Expected remote operation type.");
        }

        ClassLoader classLoader = RemoteOperation.class.getClassLoader();
        Class remoteOperationClass;
        try {
            remoteOperationClass = classLoader.loadClass(operationType);
            Constructor<RemoteOperation> constructor = remoteOperationClass.getConstructor();
            RemoteOperation remoteOperation = constructor.newInstance();
            return remoteOperation;
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Cannot load class: "        + operationType, e);
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Cannot access class: "      + operationType, e);
        } catch (InstantiationException e) {
            throw new IllegalArgumentException("Cannot instantiate class: " + operationType, e);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Cannot find constructor: "  + operationType, e);
        } catch (InvocationTargetException e) {
            throw new IllegalArgumentException("Cannot invoke constructor");
        }
    }
}